/*
*Copyright (c) 2021, Alibaba Group;
*Licensed under the Apache License, Version 2.0 (the "License");
*you may not use this file except in compliance with the License.
*You may obtain a copy of the License at

*   http://www.apache.org/licenses/LICENSE-2.0

*Unless required by applicable law or agreed to in writing, software
*distributed under the License is distributed on an "AS IS" BASIS,
*WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*See the License for the specific language governing permissions and
*limitations under the License.
*/

/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch licenses this file to you under
 * the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/*
 * Modifications Copyright Havenask Contributors. See
 * GitHub history for details.
 */

package org.havenask.test.geo;

import org.havenask.common.geo.GeoPoint;

import java.util.Random;

/**
 * Random geo generation utilities for randomized {@code geo_point} type testing
 * does not depend on jts or spatial4j. Use {@link org.havenask.test.geo.RandomShapeGenerator}
 * to create random OGC compliant shapes.
 */
public class RandomGeoGenerator {

    public static void randomPoint(Random r, double[] pt) {
        final double[] min = {-180, -90};
        final double[] max = {180, 90};
        randomPointIn(r, min[0], min[1], max[0], max[1], pt);
    }

    public static void randomPointIn(Random r, final double minLon, final double minLat,
                                     final double maxLon, final double maxLat, double[] pt) {
        assert pt != null && pt.length == 2;

        // normalize min and max
        double[] min = {normalizeLongitude(minLon), normalizeLatitude(minLat)};
        double[] max = {normalizeLongitude(maxLon), normalizeLatitude(maxLat)};
        final double[] tMin = new double[2];
        final double[] tMax = new double[2];
        tMin[0] = Math.min(min[0], max[0]);
        tMax[0] = Math.max(min[0], max[0]);
        tMin[1] = Math.min(min[1], max[1]);
        tMax[1] = Math.max(min[1], max[1]);

        pt[0] = tMin[0] + r.nextDouble() * (tMax[0] - tMin[0]);
        pt[1] = tMin[1] + r.nextDouble() * (tMax[1] - tMin[1]);
    }

    public static GeoPoint randomPoint(Random r) {
        return randomPointIn(r, -180, -90, 180, 90);
    }

    public static GeoPoint randomPointIn(Random r, final double minLon, final double minLat,
                                         final double maxLon, final double maxLat) {
        double[] pt = new double[2];
        randomPointIn(r, minLon, minLat, maxLon, maxLat, pt);
        return new GeoPoint(pt[1], pt[0]);
    }

    /** Puts latitude in range of -90 to 90. */
    private static double normalizeLatitude(double latitude) {
        if (latitude >= -90 && latitude <= 90) {
            return latitude; //common case, and avoids slight double precision shifting
        }
        double off = Math.abs((latitude + 90) % 360);
        return (off <= 180 ? off : 360-off) - 90;
    }

    /** Puts longitude in range of -180 to +180. */
    private static double normalizeLongitude(double longitude) {
        if (longitude >= -180 && longitude <= 180) {
            return longitude; //common case, and avoids slight double precision shifting
        }
        double off = (longitude + 180) % 360;
        if (off < 0) {
            return 180 + off;
        } else if (off == 0 && longitude > 0) {
            return 180;
        } else {
            return -180 + off;
        }
    }
}
